#include "TrueText/Config.h"
#include "TextImpl.h"

#include <cmath>
#include <limits>

#include <TrueText/TextLayout.h>
#include "Layouter.h"
#include "BitmapLayer.h"
#include "BlittingFunction.h"
#include "FontImpl.h"
#include "DrawersBuilder.h"
#include "Drawer.h"

namespace TrueText {

TextImpl::TextImpl(boost::shared_ptr<FontImpl> font, TextAlign align)
	: font(font)
	, clipWidth(-1)
	, align(align)
{ 
	DrawersBuilder builder(font);
	drawers = builder.Build();
}

TextImpl::TextImpl(const TextImpl& rhs, TextAlign align)
	: font(rhs.font)
	, string(rhs.string)
	, clipWidth(rhs.clipWidth)
	, drawers(rhs.drawers)
	, bounds(rhs.bounds)
	, layout(rhs.layout)
	, align(align)
{
}

const TextImpl& TextImpl::operator=(const TextImpl& rhs)
{
	if (&rhs == this)
		return *this;

	layout = rhs.layout;
	font = rhs.font;
	string = rhs.string;
	clipWidth = rhs.clipWidth;
	drawers = rhs.drawers;
	bounds = rhs.bounds;

	return *this;
}

TextImpl::~TextImpl()
{ }

void TextImpl::SetString(const std::wstring& value)
{
	string = value;
	Invalidate();
}
	
void TextImpl::SetTextAlign(TextAlign newAlign)
{
	align = newAlign;
	Invalidate();
}

TextAlign TextImpl::GetTextAlign() const
{
	return align;
}

void TextImpl::SetClipWidth(int value)
{
	clipWidth = value;
	Invalidate();
}

const TextBounds& TextImpl::GetBounds()
{
	if (NeedsRebuild())
		Rebuild();

	return bounds;
}

void TextImpl::Invalidate()
{
	layout.reset();
}

bool TextImpl::NeedsRebuild()
{
	return !layout.get();
}

void TextImpl::Rebuild()
{
	Layouter layouter(font);
	layout = layouter.Layout(clipWidth, string.c_str(), align);

	coordShift.x = 0;
	coordShift.y = 0;

	Rectangle partialBounds(
		std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), 
		std::numeric_limits<int>::min(), std::numeric_limits<int>::min());

	for (std::vector<boost::shared_ptr<Drawer> >::const_iterator drawer = drawers.begin(); drawer != drawers.end(); ++drawer)
	{
		Rectangle bounds = (*drawer)->GetBounds(*layout);

		partialBounds.left = std::min(partialBounds.left, bounds.left);
		partialBounds.top = std::min(partialBounds.top, bounds.top);
		partialBounds.right = std::max(partialBounds.right, bounds.right);
		partialBounds.bottom = std::max(partialBounds.bottom, bounds.bottom);
	}

	bounds.SetDimensions(Dimensions(partialBounds.right - partialBounds.left + 2, partialBounds.bottom - partialBounds.top + 2));
	coordShift.x = -partialBounds.left;
	coordShift.y = -partialBounds.top;
	bounds.SetTopBaselineDistance((int)(font->GetLineHeight() + (fast_float)coordShift.y));
	int lastBaseline = (int)(font->GetLineHeight() * layout->Metrics.size());

	bounds.SetBottomBaselineDistance(bounds.GetDimensions().Height - lastBaseline - coordShift.y);
}

void TextImpl::Render(const BitmapInfo& output)
{
	Point zeroOffset(0, 0);
	Render(output, zeroOffset);
}

void TextImpl::Render(const BitmapInfo& output, const Point& offset)
{
	if (NeedsRebuild())
		Rebuild();

	for (std::vector<boost::shared_ptr<Drawer> >::const_iterator drawer = drawers.begin(); drawer != drawers.end(); ++drawer)
	{
		boost::shared_ptr<BitmapLayer> layer = (*drawer)->Draw(*layout.get());
		Point localOffset = layer->GetOffset();
		localOffset.x += coordShift.x + offset.x;
		localOffset.y += coordShift.y + offset.y;
		layer->SetOffset(localOffset);
		layer->Render(output);
	}
}

const TextLayout& TextImpl::GetLayout()
{
	if (NeedsRebuild())
		Rebuild();

	return *this->layout.get();
}

}